package timing.ukulele.minio.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import timing.ukulele.minio.config.OssProperties;
import timing.ukulele.minio.dictionary.ErrorCode;
import timing.ukulele.minio.dictionary.BucketEnums;
import timing.ukulele.minio.exception.OssException;
import timing.ukulele.minio.model.MetadataInfoModel;
import timing.ukulele.minio.model.MetadataInfoSaveModel;
import timing.ukulele.minio.persistent.MetadataInfoEntity;
import timing.ukulele.minio.repository.MetadataRepository;
import timing.ukulele.minio.utils.CommonUtil;
import timing.ukulele.minio.utils.ContentTypeUtil;
import timing.ukulele.minio.vo.CompleteResultVO;
import timing.ukulele.minio.vo.FileCheckResultVO;
import timing.ukulele.minio.vo.FileMetadataInfoVO;
import timing.ukulele.minio.vo.FilePreShardingVO;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 存储组件Service层公共方法实现类
 */
@Service
public class StorageService {

    /**
     * 存储引擎Service接口定义
     */
    private final StorageEngineService storageEngineService;

    /**
     * 文件元数据服务接口定义
     */
    private final MetadataRepository fileMetadataRepository;

    /**
     * MinioPlus配置信息注入类
     */
    private final OssProperties properties;

    /**
     * 构造方法
     *
     * @param storageEngineService   存储引擎Service接口定义
     * @param fileMetadataRepository 文件元数据服务接口定义
     * @param properties             MinioPlus配置类
     */
    public StorageService(StorageEngineService storageEngineService, MetadataRepository fileMetadataRepository, OssProperties properties) {
        this.storageEngineService = storageEngineService;
        this.fileMetadataRepository = fileMetadataRepository;
        this.properties = properties;
    }

    /**
     * 文件预分片
     *
     * @param fileSize 文件大小
     * @return 预分片结果
     */
    public FilePreShardingVO sharding(long fileSize) {

        // 计算分块数量
        Integer chunkNum = storageEngineService.computeChunkNum(fileSize);

        List<FilePreShardingVO.Part> partList = new ArrayList<>();

        long start = 0;
        for (int partNumber = 1; partNumber <= chunkNum; partNumber++) {

            long end = Math.min(start + properties.getPart().getSize(), fileSize);

            FilePreShardingVO.Part part = new FilePreShardingVO.Part();
            // 开始位置
            part.setStartPosition(start);
            // 结束位置
            part.setEndPosition(end);

            // 更改下一次的开始位置
            start = start + properties.getPart().getSize();
            partList.add(part);
        }

        FilePreShardingVO filePreShardingVo = new FilePreShardingVO();
        filePreShardingVo.setFileSize(fileSize);
        filePreShardingVo.setPartCount(chunkNum);
        filePreShardingVo.setPartSize(properties.getPart().getSize());
        filePreShardingVo.setPartList(partList);

        return filePreShardingVo;
    }

    /**
     * 上传任务初始化
     *
     * @param fileMd5      文件md5
     * @param fullFileName 文件名（含扩展名）
     * @param fileSize     文件长度
     * @param isPrivate    是否私有 false:否 true:是
     * @param userId       用户编号
     * @return {@link FileCheckResultVO}
     */
    public FileCheckResultVO init(String fileMd5, String fullFileName, long fileSize, Boolean isPrivate, String userId) {

        // isPrivate 为空时，设置为 false
        isPrivate = isPrivate != null && isPrivate;

        FileCheckResultVO resultVo = storageEngineService.init(fileMd5, fullFileName, fileSize, isPrivate, userId);

        if (resultVo != null) {
            for (FileCheckResultVO.Part part : resultVo.getPartList()) {
                part.setUrl(remakeUrl(part.getUrl()));
            }
        }

        return resultVo;
    }

    /**
     * 合并已分块的文件
     *
     * @param fileKey     文件关键
     * @param partMd5List 文件分块md5列表
     * @param userId      用户编号
     * @return {@link CompleteResultVO}
     */
    public CompleteResultVO complete(String fileKey, List<String> partMd5List, String userId) {
        CompleteResultVO completeResultVo = storageEngineService.complete(fileKey, partMd5List, userId);

        if (completeResultVo != null) {
            for (FileCheckResultVO.Part part : completeResultVo.getPartList()) {
                part.setUrl(remakeUrl(part.getUrl()));
            }
        }

        return completeResultVo;
    }

    /**
     * 删除未上传完成的文件
     *
     * @param fileKey 文件的key
     */
    public void deleteNotComplete(String fileKey) {
        MetadataInfoEntity metaInfo = fileMetadataRepository.lambdaQuery().eq(MetadataInfoEntity::getFileKey, fileKey).last(" limit 1").one();
        if (metaInfo != null) {
            fileMetadataRepository.remove(metaInfo.getId());
            if (StringUtils.hasText(metaInfo.getUploadTaskId()))
                storageEngineService.deleteNotComplete(metaInfo.getStorageBucket(),
                        CommonUtil.getObjectName(metaInfo.getStoragePath(), metaInfo.getFileMd5(), metaInfo.getFileSuffix()),
                        metaInfo.getUploadTaskId());
        }
    }

    /**
     * 取得文件下载地址
     *
     * @param fileKey 文件KEY
     * @param userId  用户编号
     * @return 文件下载地址
     */
    public String download(String fileKey, String userId) {
        return remakeUrl(storageEngineService.download(fileKey, userId));
    }

    /**
     * 取得原图地址
     *
     * @param fileKey 文件KEY
     * @param userId  用户编号
     * @return 原图地址
     */
    public String image(String fileKey, String userId) {
        return remakeUrl(storageEngineService.image(fileKey, userId));
    }

    /**
     * 取得缩略图地址
     *
     * @param fileKey 文件KEY
     * @param userId  用户编号
     * @return 缩略图地址
     */
    public String preview(String fileKey, String userId) {
        return remakeUrl(storageEngineService.preview(fileKey, userId));
    }

    /**
     * 查询元数据信息
     *
     * @param key 文件key
     * @return 文件元数据信息
     */
    public FileMetadataInfoVO one(String key) {

        MetadataInfoModel fileMetadataInfo = new MetadataInfoModel();
        fileMetadataInfo.setFileKey(key);
        return fileMetadataRepository.one(fileMetadataInfo);

    }

    /**
     * 查询元数据信息
     *
     * @param fileMetadataInfo 查询入参
     * @return 文件元数据信息集合
     */
    public List<FileMetadataInfoVO> list(MetadataInfoModel fileMetadataInfo) {
        // 列表查询，取得全部符合条件的数据
        return fileMetadataRepository.list(fileMetadataInfo);
    }

    /**
     * 创建文件
     * 尽量不要用本方法处理大文件，大文件建议使用前端直传
     *
     * @param fullFileName 文件名（含扩展名）
     * @param isPrivate    是否私有 false:否 true:是
     * @param userId       用户编号
     * @param fileBytes    文件字节流
     * @return 文件元数据信息
     */
    public FileMetadataInfoVO createFile(String fullFileName, Boolean isPrivate, String userId, byte[] fileBytes) {

        // 组装文件保存入参
        MetadataInfoSaveModel saveDTO = buildSaveDto(fullFileName, isPrivate, userId, fileBytes);

        // 查询MinIO中是否存在相同MD5值的文件
        MetadataInfoModel fileMetadataInfo = new MetadataInfoModel();
        fileMetadataInfo.setFileMd5(saveDTO.getFileMd5());
        fileMetadataInfo.setIsFinished(true);
        FileMetadataInfoVO alreadyFile = fileMetadataRepository.one(fileMetadataInfo);

        if (null != alreadyFile) {
            saveDTO.setStorageBucket(alreadyFile.getStorageBucket());
            saveDTO.setStoragePath(alreadyFile.getStoragePath());
        } else {
            // 新文件时，执行写入逻辑
            storageEngineService.createFile(saveDTO, fileBytes);
        }

        return fileMetadataRepository.save(saveDTO);

    }

    /**
     * 创建文件
     * 尽量不要用本方法处理大文件，大文件建议使用前端直传
     *
     * @param fullFileName 文件名（含扩展名）
     * @param isPrivate    是否私有 false:否 true:是
     * @param userId       用户编号
     * @param inputStream  文件输入字节流
     * @return 文件元数据信息
     */
    public FileMetadataInfoVO createFile(String fullFileName, Boolean isPrivate, String userId, InputStream inputStream) {
        return createFile(fullFileName, isPrivate, userId, IoUtil.readBytes(inputStream));
    }

    /**
     * 创建文件
     * 尽量不要用本方法处理大文件，大文件建议使用前端直传
     *
     * @param fullFileName 文件名（含扩展名）
     * @param isPrivate    是否私有 false:否 true:是
     * @param userId       用户编号
     * @param url          文件地址
     * @return 文件元数据信息
     */
    public FileMetadataInfoVO createFile(String fullFileName, Boolean isPrivate, String userId, String url) {
        // 请求文件
        HttpResponse httpResponse = HttpUtil.createGet(url).execute();
        // 获得输入流
        InputStream inputStream = httpResponse.bodyStream();
        // 调用处理函数
        return createFile(fullFileName, isPrivate, userId, inputStream);
    }

    /**
     * 创建大文件
     * 大文件建议使用本方法
     *
     * @param fullFileName 文件名（含扩展名）
     * @param md5          文件名MD5
     * @param fileSize     文件名大小
     * @param isPrivate    是否私有 false:否 true:是
     * @param userId       用户编号
     * @param inputStream  文件输入字节流
     * @return 文件元数据信息
     */
    public FileMetadataInfoVO createBigFile(String fullFileName, String md5, long fileSize, Boolean isPrivate, String userId, InputStream inputStream) {
        // 组装文件保存入参
        MetadataInfoSaveModel saveDTO = buildSaveDto(fullFileName, md5, fileSize, isPrivate, userId);

        // 查询MinIO中是否存在相同MD5值的文件
        MetadataInfoModel fileMetadataInfo = new MetadataInfoModel();
        fileMetadataInfo.setFileMd5(saveDTO.getFileMd5());
        fileMetadataInfo.setIsFinished(true);
        FileMetadataInfoVO alreadyFile = fileMetadataRepository.one(fileMetadataInfo);


        if (null != alreadyFile) {
            saveDTO.setStorageBucket(alreadyFile.getStorageBucket());
            saveDTO.setStoragePath(alreadyFile.getStoragePath());
        } else {
            // 新文件时，执行写入逻辑
            storageEngineService.createFile(saveDTO, inputStream);
        }

        return fileMetadataRepository.save(saveDTO);
    }

    /**
     * 根据文件key读取文件字节流
     *
     * @param fileKey 文件key
     * @return 文件字节流
     */
    public Pair<FileMetadataInfoVO, byte[]> read(String fileKey) {
        return storageEngineService.read(fileKey);
    }

    /**
     * 根据文件key删除文件
     *
     * @param fileKey 文件key
     * @return 是否成功
     */
    public Boolean remove(String fileKey) {
        return storageEngineService.remove(fileKey);
    }

    MetadataInfoSaveModel buildSaveDto(String fullFileName, Boolean isPrivate, String userId, byte[] fileBytes) {

        if (null == fileBytes) {
            throw new OssException(ErrorCode.FILE_BYTES_FAILED);
        }
        // 计算文件MD5值
        String md5 = SecureUtil.md5().digestHex(fileBytes);

        return buildSaveDto(fullFileName, md5, fileBytes.length, isPrivate, userId);
    }

    MetadataInfoSaveModel buildSaveDto(String fullFileName, String md5, long fileSize, Boolean isPrivate, String userId) {

        // 生成UUID作为文件KEY
        String key = IdUtil.fastSimpleUUID();
        String suffix = FileUtil.getSuffix(fullFileName);
        String fileMimeType = ContentTypeUtil.getContentType(suffix);

        // 根据文件后缀取得桶
        String storageBucket = BucketEnums.getBucketByFileSuffix(suffix);

        // 取得存储路径
        String storagePath = CommonUtil.getPathByDate();

        // 是否存在缩略图
        Boolean isPreview = properties.getThumbnail().isEnable() && BucketEnums.IMAGE.getCode().equals(storageBucket);

        // 创建文件元数据信息
        MetadataInfoSaveModel fileMetadataInfoSaveDTO = new MetadataInfoSaveModel();
        fileMetadataInfoSaveDTO.setFileKey(key);
        fileMetadataInfoSaveDTO.setFileMd5(md5);
        fileMetadataInfoSaveDTO.setFileName(fullFileName);
        fileMetadataInfoSaveDTO.setFileMimeType(fileMimeType);
        fileMetadataInfoSaveDTO.setFileSuffix(suffix);
        fileMetadataInfoSaveDTO.setFileSize(fileSize);
        fileMetadataInfoSaveDTO.setStorageBucket(storageBucket);
        fileMetadataInfoSaveDTO.setStoragePath(storagePath);
        fileMetadataInfoSaveDTO.setUploadTaskId("");
        fileMetadataInfoSaveDTO.setIsFinished(true);
        fileMetadataInfoSaveDTO.setIsPart(false);
        fileMetadataInfoSaveDTO.setPartNumber(0);
        fileMetadataInfoSaveDTO.setIsPreview(isPreview);
        fileMetadataInfoSaveDTO.setIsPrivate(isPrivate);
        fileMetadataInfoSaveDTO.setCreateBy(userId);
        fileMetadataInfoSaveDTO.setUpdateBy(userId);

        return fileMetadataInfoSaveDTO;
    }

    /**
     * 重写文件地址
     *
     * @param url 文件地址
     * @return 重写后的文件地址
     */
    private String remakeUrl(String url) {

        if (CharSequenceUtil.isNotBlank(properties.getBaseUrl())) {
            return url.replace(properties.getEndpoint(), properties.getBaseUrl());
        }
        return url;
    }
}
